{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center>Автор материала: Шаймарданова Екатерина (@ekaterina)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>TeaPOT</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Что такое автоматизированное машинное обучение (automated ml)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теория обучения машин (machine learning, машинное обучение) находится на стыке прикладной статистики, численных методов оптимизации, дискретного анализа, и за последние 50 лет оформилась в самостоятельную математическую дисциплину. Методы машинного обучения составляют основу еще более молодой дисциплины — интеллектуального анализа данных (data mining).\n",
    "Перенося эти определения на automated machine learning (AML), можно сказать, что это − «Теория автоматического / автоматизированного обучения машин».\n",
    "\n",
    "Чтобы применять машинное обучение для решения конкретных кейсов, необходимо постоянно:\n",
    "\n",
    "* Настраивать гиперпараметры моделей;\n",
    "* Пробовать новые алгоритмы;\n",
    "* Добавлять в модели различные представления исходных признаков\n",
    "\n",
    "От этих рутинных операций, как и от части операций в подготовке и очистке данных, аналитиков или data scientist’ов можно избавить с помощью автоматизации.\n",
    "\n",
    "Автоматизированный алгоритм может перебрать все стандартные комбинации и выдать некоторое базовое решение, которое квалифицированный специалист сможет взять за основу и дальше улучшать. Однако во многих случаях результатов работы автоматизированного алгоритма окажется достаточно и без дополнительных улучшений, и их можно будет использовать непосредственно.\n",
    "\n",
    "На сегодняшний день можно выделить два наиболее результативных пакета автоматизированного машинного обучения. Оба они используют библиотеку машинного обучения sklearn языка Python и активно разрабатываются.\n",
    "Первый из них — библиотека Auto-sklearn, разработанный во Фрайбургском университете.\n",
    "Вторым лидирующим решением в области автоматизированного машинного обучения выступает библиотека TeaPOT (TPOT).\n",
    "\n",
    "Как определяют TPOT сами авторы - это инструмент Python, который автоматически создает и оптимизирует конвейеры (pipeline) машинного обучения с использованием генетического программирования. TPOT автоматизирует самую утомительную часть машинного обучения, исследуя тысячи возможных конвейеров, чтобы найти лучший для ваших данных."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![img](https://raw.githubusercontent.com/rhiever/tpot/master/images/tpot-ml-pipeline.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Установка TPOT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Требует предварительной установки следующих пакетов: NumPy, SciPy, scikit-learn, DEAP, update_checker, tqdm, pywin32 (для Windows), xgboost, scikit-mdr и skrebate:\n",
    "\n",
    "* conda install numpy scipy scikit-learn\n",
    "* pip install deap update_checker tqdm\n",
    "* pip install pywin32\n",
    "* pip install xgboost\n",
    "* pip install scikit-mdr skrebate\n",
    "* pip install tpot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Пример использования на Iris Flower Classification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.model_selection import train_test_split\n",
    "from tpot import TPOTClassifier\n",
    "\n",
    "iris = load_iris()\n",
    "X_train, X_test, y_train, y_test = train_test_split(iris.data.astype(np.float64),\n",
    "    iris.target.astype(np.float64), train_size=0.75, test_size=0.25)\n",
    "\n",
    "tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2)\n",
    "tpot.fit(X_train, y_train)\n",
    "print(tpot.score(X_test, y_test))\n",
    "tpot.export('tpot_iris_pipeline.py') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Содержание 'tpot_iris_pipeline.py' (может отличаться - не зафиксировала random seed):\n",
    "#import numpy as np\n",
    "\n",
    "#from sklearn.model_selection import train_test_split\n",
    "#from sklearn.naive_bayes import GaussianNB\n",
    "#from sklearn.pipeline import make_pipeline\n",
    "#from sklearn.preprocessing import Normalizer\n",
    "\n",
    "# NOTE: Make sure that the class is labeled 'class' in the data file\n",
    "#tpot_data = np.recfromcsv('PATH/TO/DATA/FILE', delimiter='COLUMN_SEPARATOR', dtype=np.float64)\n",
    "#features = np.delete(tpot_data.view(np.float64).reshape(tpot_data.size, -1), tpot_data.dtype.names.index('class'), axis=1)\n",
    "#training_features, testing_features, training_classes, testing_classes = \\\n",
    "#    train_test_split(features, tpot_data['class'], random_state=42)\n",
    "\n",
    "#exported_pipeline = make_pipeline(\n",
    "#    Normalizer(norm=\"l2\"),\n",
    "#    GaussianNB()\n",
    "#)\n",
    "\n",
    "#exported_pipeline.fit(training_features, training_classes)\n",
    "#results = exported_pipeline.predict(testing_features)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получаем точность при тестировании примерно 97%."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Описание некоторых параметров TPOT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* generations - любое положительное целое число - Число итераций в процессе оптимизации конвейерного процесса. Как правило, TPOT будет работать лучше, если вы дадите ему больше поколений (и, следовательно, времени), чтобы оптимизировать конвейер.\n",
    "* population_size - любое положительное целое число - Количество особей, оставшихся в популяции для каждого поколения.\n",
    "* offspring_size - любое положительное целое число - Число потомков в каждом поколении.\n",
    "* verbosity - Какое количество информации выдает TPOT при запуске. 0 = none, 1 = minimal, 2 = high, 3 = all.\n",
    "* mutation_rate - [0.0,1.0] - Скорость мутации алгоритма. Для какого количества конвейеров применяются случайные изменения в каждом поколении. Рекомендуется оставить значение по-умолчанию.\n",
    "* crossover_rate - [0.0,1.0] - Скорость селекции для алгоритма. Показывает какое количество конвейеров \"порождает\" каждое поколение. Также рекомендуется оставить значение по-умолчанию."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Пример использования на данных соревнования Catch Me If You Can"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# загрузим обучающую и тестовую выборки\n",
    "train_df = pd.read_csv('../../data/websites_train_sessions.csv',\n",
    "                       index_col='session_id')\n",
    "test_df = pd.read_csv('../../data/websites_test_sessions.csv',\n",
    "                      index_col='session_id')\n",
    "\n",
    "# приведем колонки time1, ..., time10 к временному формату\n",
    "times = ['time%s' % i for i in range(1, 11)]\n",
    "train_df[times] = train_df[times].apply(pd.to_datetime)\n",
    "test_df[times] = test_df[times].apply(pd.to_datetime)\n",
    "\n",
    "# отсортируем данные по времени\n",
    "train_df = train_df.sort_values(by='time1')\n",
    "\n",
    "# посмотрим на заголовок обучающей выборки\n",
    "train_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "# приведем колонки site1, ..., site10 к целочисленному формату и заменим пропуски нулями\n",
    "sites = ['site%s' % i for i in range(1, 11)]\n",
    "train_df[sites] = train_df[sites].fillna(0).astype('int')\n",
    "test_df[sites] = test_df[sites].fillna(0).astype('int')\n",
    "\n",
    "# загрузим словарик сайтов\n",
    "with open(r\"../../data/site_dic.pkl\", \"rb\") as input_file:\n",
    "    site_dict = pickle.load(input_file)\n",
    "\n",
    "# датафрейм словарика сайтов\n",
    "sites_dict = pd.DataFrame(list(site_dict.keys()), index=list(site_dict.values()), columns=['site'])\n",
    "print(u'всего сайтов:', sites_dict.shape[0])\n",
    "sites_dict.head()\n",
    "\n",
    "# создадим отдельный датафрейм, где будем работать со временем\n",
    "time_df = pd.DataFrame(index=train_df.index)\n",
    "time_df['target'] = train_df['target']\n",
    "\n",
    "# найдем время начала и окончания сессии\n",
    "time_df['min'] = train_df[times].min(axis=1)\n",
    "time_df['max'] = train_df[times].max(axis=1)\n",
    "\n",
    "# вычислим длительность сессии и переведем в секунды\n",
    "time_df['seconds'] = (time_df['max'] - time_df['min']) / np.timedelta64(1, 's')\n",
    "\n",
    "#time_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# наша целевая переменная\n",
    "\n",
    "train_df.rename(columns={'target': 'class'}, inplace=True)\n",
    "y_train = train_df['class']\n",
    "\n",
    "\n",
    "# объединенная таблица исходных данных\n",
    "full_df = pd.concat([train_df.drop('class', axis=1), test_df])\n",
    "\n",
    "# индекс, по которому будем отделять обучающую выборку от тестовой\n",
    "idx_split = train_df.shape[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# табличка с индексами посещенных сайтов в сессии\n",
    "full_sites = full_df[sites]\n",
    "full_sites.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.sparse import csr_matrix, hstack\n",
    "\n",
    "# последовательность с индексами\n",
    "sites_flatten = full_sites.values.flatten()\n",
    "\n",
    "# искомая матрица\n",
    "full_sites_sparse = csr_matrix(([1] * sites_flatten.shape[0],\n",
    "                                sites_flatten,\n",
    "                                range(0, sites_flatten.shape[0]  + 10, 10)))[:, 1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Построение модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tpot import TPOTClassifier\n",
    "\n",
    "\n",
    "def get_tpot(X, y,seed=42, ratio = 0.9):\n",
    "    # разделим выборку на обучающую и валидационную\n",
    "    idx = round(X.shape[0] * ratio)\n",
    "    # обучение классификатора\n",
    "    tpot = TPOTClassifier(generations = 2, population_size = 3, verbosity=2,max_time_mins=3, max_eval_time_mins=0.04)\n",
    "    tpot.fit(X[:idx, :], y[:idx])\n",
    "    \n",
    "    return tpot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# выделим из объединенной выборки только обучающую (для которой есть ответы)\n",
    "X_train = full_sites_sparse[:idx_split, :]\n",
    "\n",
    "tpot = get_tpot(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# функция для записи прогнозов в файл\n",
    "def write_to_submission_file(predicted_labels, out_file,\n",
    "                             target='target', index_label=\"session_id\"):\n",
    "    predicted_df = pd.DataFrame(predicted_labels,\n",
    "                                index = np.arange(1, predicted_labels.shape[0] + 1),\n",
    "                                columns=[target])\n",
    "    predicted_df.to_csv(out_file, index_label=index_label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_test = full_sites_sparse[idx_split:,:]\n",
    "y_test = tpot.predict(X_test)\n",
    "write_to_submission_file(y_test, 'tpot_baseline.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Вывод"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В результате не удалось побить даже первый бейзлайн. Score = 0.5. Возможно TPOT так работает на разреженных матрицах. В любом случае дальше нужно подбирать параметры классификатора самостоятельно, либо давать свой словарь TPOT-у (параметр config_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Ссылки\n",
    "* [Будут ли data scientist’ы в ближайшее время заменены автоматизированными алгоритмами и искусственным интеллектом?](https://habrahabr.ru/company/npl/blog/322414/)\n",
    "* [Automated Machine Learning − как оно есть](http://ko.com.ua/automated_machine_learning_kak_ono_est_116151)\n",
    "* [ML Wiki](http://www.machinelearning.ru/wiki/index.php?title=%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%28%D0%BA%D1%83%D1%80%D1%81_%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D0%B9%2C_%D0%9A.%D0%92.%D0%92%D0%BE%D1%80%D0%BE%D0%BD%D1%86%D0%BE%D0%B2%29)\n",
    "* [TPOT Github](http://rhiever.github.io/tpot/)"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
